- 为什么要使用模版
- 关键字 typename
- C++ 11新的模版特性 - 变参模板
- C++ 11新的模板特性 - 外部模板
- C++ 11新的模版特性 - 模版别名
- C++ 11新的模板特性 - 默认模板实参
- C++ 11新的模板特性 - 回返类型后置的函数声明
- C++ 11新的模板特性 - 用于元编程的类别属性
- 感谢
为什么要使用模版
在声明变量、函数和大多数其他类型的实体的时候,C++要求我们使用特定的类型。然而许多代码除了类型不同不同之外,其余代码都看起来是相同的。
比如sort()
函数,每当你实现了一个顺序容器,就需要重新写一个sort()
函数……
如果程序设计语言并不支持这个语言特性,为了实现相同的功能,你只能使用下面这些糟糕的替代方法:
- 针对每个所需相同行为的不同类型,你可以一次又一次地实现它。
- 你可以把通用代码放在一个注入Object或者void*的公共基础类里面。
- 你可以使用特殊的预处理程序,如#define。
而在Java和C等类似的语言中,上面的方法都有自身的缺点:
- 做了许多重复工作,会趋向舍弃复杂但更好用的算法来保证尽可能不出错,复杂算法通常都趋向于引入更多的错误。
- 采用公共基类编写代码,会失去类型检查这个优点。另外,对于以后实现的许多类都必须继承自某个特定的基类,这会令代码的维护更加困难。
- 如如果使用了预处理程序,你会使代码变得格式混乱难以阅读,使用“文本替换机制”并不会考虑作用域和类型。
在现今的程序中,模版的应用非常广泛。C++的标准库中,几乎所有代码都是模版代码。
关键字 typename
关键字typename
用来指明紧跟其后的名称是个类型的名称。最常用在template
之后,比如:
我们应该尽可能使用typename
取代class
在template
后面的尖括号中的应用。
当然,关键字typename
不仅仅只有这一个用法,还可以像下面的方式进行使用:
在这里typename
用来阐明“SubType
是个类型,定义于class T
内”。因此,ptr
是个指针,指向类型为T::SubType
。
如果没有
typename
,则SubType
会被视为一个static
成员,于是T::SubType *ptr
被视为“类型T
的数值SubType
”乘以ptr
。
“基于SubType
必须是个类型”这样的限定,任何被用来替换T
的类型,都必须提供一个内层类型SubType
。比如下面的Test
类:
12345 class Test {public:void print() {}using SubType = int;};在这里,
using SubType = int;
等价于typedef int SubType;
。C++ 11的关键字using的新的用法比原来的typedef
更加强大,建议使用。
C++ 11新的模版特性 - 变参模板
自C++11起,template
可拥有那种“得以接受不确定个数的template
实参”的个数。此能力被称为 variadic template。下面代码是print()
使用变参模版的实现方式:
这是一种递归写法,必须提供一个不用template
的重载函数print()
,才能结束整个递归过程。如同下面的使用一个用了template
的print()
的方法虽然可能通过编译,但不推荐:
并不确定在只有一个参数的时候,该调用哪一个print()
。这种会形成歧义的代码应当避免。
在实验楼的《C++ 11/14 高速上手教程中-语言可用性的强化》(需要账号登录)提供了一种使用std::initializer_list
和lambda
表达式的写法(修改版):
这个写法利用了
std::initializer_list
对args
的展开...
所用到的初始化列表构造函数,由于std::initializer_list<int>
在这里接受的是int
类型的初始化列表,所以匿名lambda
表达式返回整数0用于填充std::initializer_list
。
但在上面的教程说道,可以使用std::bind
和完美转发进行args
的...
操作并转发。没想到该如何做。另外,该做法虽然简洁,但性能问题并不确定,如需使用请通过大规模的调用进行与递归式的测试进行比较,选用最合适的方法。
C++ 11新的模板特性 - 外部模板
在标准C++中,只要在编译单元内遇到被完整定义的模板,编译器都必须将其实例化(instantiate)。这会大大增加编译时间,特别是模板在许多编译单元内使用相同的参数实例化。看起来没有办法告诉C++不要引发模板的实例化。
C++11将会引入外部模板这一概念。C++已经有了强制编译器在特定位置开始实例化的语法:
而C++所缺乏的是阻止编译器在某个编译单元内实例化模板的能力。C++11将简单地扩充前文语法如下:
这样就告诉编译器不要在该编译单元内将该模板实例化。使用方法如下:
extern
有两个作用: 第一个,当它与"C"
一起连用时,如:extern "C" void fun(int a, int b);
则告诉编译器在编译fun
这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun
这个名字变得面目全非,可能是fun@aBc_int_int#%$
也可能是别的,这要看编译器的”脾气”了(不同的编译器采用的方法不一样),为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释! 第二,当extern
不与"C"
在一起修饰变量或函数时,如在头文件中:extern int g_Int;
它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。
C++ 11新的模版特性 - 模版别名
自C++11起,支持使用关键字using
来命名一个模板的Alias Template
,示例如下:
上面这样的使用方法,可以避免像STL的vector
源码中的各种#define
,能很大程度提高模板的可读性。
C++ 11新的模板特性 - 默认模板实参
这里应该提一下以前的一个关于模板的特性,非类型模板参数(Nontype Tempalte Parameter), 声明方法如下:
这样在使用SuckType
的时候,就可以使用int
值使用SuckType
类模板生成一个类型:
这种用法,在线性代数库 Eigen 中的矩阵模板大规模进行使用。
C++ 11新的模板特性 - 回返类型后置的函数声明
C++11引进一种新的函数定义与声明的语法:
用于解决模板类型推导的实现。这种语法也能套用到一般的函数定义与声明:
C++ 11新的模板特性 - 用于元编程的类别属性
模版元编程(template metaprogram)是C++中最复杂也是威力最强大的编程范式,它是一种可以创建和操纵程序的程序。模版元编程完全不同于普通的运行期程序,它很独特,因为模版元程序的执行完全是在编译期,并且模版元程序操纵的数据不能是运行时变量,只能是编译期常量,不可修改,另外它用到的语法元素也是相当有限,不能使用运行期的一些语法,比如if-else,for等语句都不能用。因此,模版元编程需要很多技巧,常常需要类型重定义、枚举常量、继承、模板偏特化等方法来配合,因此编写模版元编程比较复杂也比较困难。
模版元程序由元数据和元函数组成,元数据就是元编程可以操作的数据,即C++编译器在编译期可以操作的数据。元数据不是运行期变量,只能是编译期常量,不能修改,常见的元数据有enum
枚举常量、静态常量、基本类型和自定义类型等。
元函数是模板元编程中用于操作处理元数据的“构件”,可以在编译期被“调用”,因为它的功能和形式和运行时的函数类似,而被称为元函数,它是元编程中最重要的构件。元函数实际上表现为C++的一个类、模板类或模板函数:
调用元函数获取value
值:cout<<meta_func<1, 2>::value<<endl;
模板元编程产生的源程序是在编译期执行的程序,因此它首先要遵循C++和模板的语法,但是它操作的对象不是运行时普通的变量,因此不能使用运行时的C++关键字(如if、else、for),可用的语法元素相当有限,最常用的是:
enum
、static
、const
,用来定义编译期的整数常量;typedef/using
,用于定义元数据;T
、Args...
,声明元数据类型;template
,主要用于定义元函数;::
,域运算符,用于解析类型作用域获取计算结果(元数据)。
模板元编程中的
if-else
等逻辑通过type_traits
来实现,它不仅仅可以在编译期做判断,还可以做计算、查询、转换和选择。type_traits
是C++11提供的模板元基础库,通过type_traits
可以实现在编译期计算、查询、判断、转换和选择,提供了模板元编程需要的一些常用元函数。模板元中的
for
等逻辑可以通过递归、重载、和模板特化(偏特化)等方法实现。
这个元编程需要仔细再看看。